import network
import ujson as json
import uasyncio as asyncio
import machine
import time
import os
import sys
import gc
import socket
import ubinascii
import urandom
import _thread
from machine import WDT, Pin

_device_loader = None

# ==================== 配置管理器 ====================
class ConfigManager:
    """配置管理器 - 负责配置的保存、加载和默认值"""
    
    CONFIG_FILE = "/config.json"
    
    DEFAULT_CONFIG = {
        # 设备信息 - 必须手动配置
        "device_id": "",  # 必填，用户需在配网界面输入
        "device_type": "ESP32-S3",
        "device_name": "ESP32智能设备",
        "firmware_version": "3.1.0",
        
        # WiFi配置
        "wifi_ssid": "",
        "wifi_password": "",
        "wifi_timeout": 30,
        
        # 服务器配置
        "server_mode": "cloud",  # "local" 或 "cloud"
        "server_host_local": "192.168.1.100",
        "server_host_cloud": "115.190.181.211",
        "server_port": 11451,
        "api_key": "",
        
        # 看门狗配置
        "watchdog_enabled": True,
        "watchdog_timeout": 30000,
        "watchdog_feed_interval": 5,
        
        # 心跳配置
        "heartbeat_interval": 30,
        
        # GC配置
        "gc_interval": 30,
        
        # 插件配置
        "plugin_dir": "/plugins",
        "plugin_auto_load": True,
        
        # WebSocket配置
        "ws_reconnect_max_attempts": 10,
        "ws_send_queue_size": 64,
        
        # 缓冲配置
        "event_buffer_size": 50,
        
        # 日志配置
        "log_level": "INFO",
        "log_to_server": True,
        "debug_mode": False
    }
    
    @classmethod
    def load(cls):
        """加载配置文件"""
        try:
            with open(cls.CONFIG_FILE, 'r') as f:
                config = json.load(f)
            
            # 合并默认配置（处理新增字段）
            for key, value in cls.DEFAULT_CONFIG.items():
                if key not in config:
                    config[key] = value
            
            print("[配置] 加载成功: %s" % cls.CONFIG_FILE)
            return config
            
        except OSError:
            print("[配置] 文件不存在，使用默认配置")
            return cls.DEFAULT_CONFIG.copy()
        except Exception as e:
            print("[配置] 加载失败: %s，使用默认配置" % e)
            return cls.DEFAULT_CONFIG.copy()
    
    @classmethod
    def save(cls, config):
        """保存配置文件"""
        try:
            with open(cls.CONFIG_FILE, 'w') as f:
                json.dump(config, f)
            print("[配置] 保存成功: %s" % cls.CONFIG_FILE)
            return True
        except Exception as e:
            print("[配置] 保存失败: %s" % e)
            return False
    
    @classmethod
    def exists(cls):
        """检查配置文件是否存在"""
        try:
            os.stat(cls.CONFIG_FILE)
            return True
        except OSError:
            return False
    
    @classmethod
    def validate(cls, config):
        """验证配置完整性"""
        required_fields = ["device_id", "wifi_ssid"]
        missing = [f for f in required_fields if not config.get(f)]
        
        if missing:
            print("[配置] 缺少必要配置: %s" % ", ".join(missing))
            return False
        
        # 验证device_id格式（只允许字母、数字、下划线、中划线）
        device_id = config.get("device_id", "")
        if not device_id or len(device_id) < 3:
            print("[配置] device_id 长度不足（至少3个字符）")
            return False
        
        return True
    
    @classmethod
    def generate_ap_suffix(cls):
        """生成AP热点名称后缀（使用MAC地址）"""
        mac = ubinascii.hexlify(machine.unique_id()).decode()
        return mac[-4:].upper()

# ==================== 配网模式 ====================
class ConfigPortal:
    """配网门户 - 提供AP模式和Web配置界面"""
    
    def __init__(self):
        self.ap = None
        self.server_socket = None
        self.running = False
    
    def start(self):
        """启动配网门户"""
        print("\n" + "=" * 60)
        print("进入配网模式".center(60))
        print("=" * 60)
        
        # 创建AP
        self.ap = network.WLAN(network.AP_IF)
        self.ap.active(True)
        
        ap_suffix = ConfigManager.generate_ap_suffix()
        ap_ssid = "ESP32-Setup-%s" % ap_suffix
        ap_password = "12345678"
        
        self.ap.config(essid=ap_ssid, password=ap_password, authmode=network.AUTH_WPA_WPA2_PSK)
        
        while not self.ap.active():
            time.sleep(0.1)
        
        print("[AP] SSID: %s" % ap_ssid)
        print("[AP] 密码: %s" % ap_password)
        print("[AP] IP地址: 192.168.4.1")
        print("[提示] 请连接到此WiFi，然后访问 http://192.168.4.1")
        print("=" * 60 + "\n")
        
        # 启动Web服务器
        self.running = True
        self._run_server()
    
    def _run_server(self):
        """运行Web配置服务器"""
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.bind(('0.0.0.0', 80))
        self.server_socket.listen(5)
        self.server_socket.settimeout(1.0)
        
        print("[Web] 服务器启动在 http://192.168.4.1\n")
        
        while self.running:
            try:
                client, addr = self.server_socket.accept()
                self._handle_client(client)
            except OSError:
                continue
            except Exception as e:
                print("[Web] 错误: %s" % e)
    
    def _handle_client(self, client):
        """处理HTTP客户端请求"""
        try:
            client.settimeout(3.0)
            request = client.recv(2048).decode('utf-8')
            
            if not request:
                return
            
            # 解析请求
            lines = request.split('\r\n')
            if not lines:
                return
            
            try:
                method, path, _ = lines[0].split(' ', 2)
            except:
                print("[Web] 请求解析失败")
                return
            
            # 处理GET请求 - 显示配置页面
            if method == 'GET' and path == '/':
                response = self._generate_config_page()
                client.send(response.encode())
            
            # 处理POST请求 - 保存配置
            elif method == 'POST' and path == '/save':
                # 提取POST数据
                body = request.split('\r\n\r\n', 1)[1] if '\r\n\r\n' in request else ''
                
                result = self._save_config_from_post(body)
                if result['success']:
                    response = self._generate_success_page()
                else:
                    response = self._generate_error_page(result.get('error', '保存失败'))
                
                client.send(response.encode())
                
                # 成功后延迟重启
                if result['success']:
                    time.sleep(2)
                    self.running = False
                    machine.reset()
            
            # 获取当前配置（JSON API）
            elif method == 'GET' and path == '/config':
                config = ConfigManager.load()
                response = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"
                response += json.dumps(config)
                client.send(response.encode())
            
            else:
                response = "HTTP/1.1 404 Not Found\r\n\r\n404 Not Found"
                client.send(response.encode())
            
        except Exception as e:
            print("[Web] 处理请求失败: %s" % e)
            import sys
            sys.print_exception(e)
        finally:
            try:
                client.close()
            except:
                pass
    
    def _url_decode(self, text):
        """简单的URL解码"""
        # 替换+为空格
        text = text.replace('+', ' ')
        # 处理%编码
        result = []
        i = 0
        while i < len(text):
            if text[i] == '%' and i + 2 < len(text):
                try:
                    hex_str = text[i+1:i+3]
                    char_code = int(hex_str, 16)
                    result.append(chr(char_code))
                    i += 3
                except:
                    result.append(text[i])
                    i += 1
            else:
                result.append(text[i])
                i += 1
        return ''.join(result)
    
    def _save_config_from_post(self, body):
        """从POST数据保存配置"""
        try:
            config = ConfigManager.load()
            
            # 解析表单数据
            params = {}
            for item in body.split('&'):
                if '=' in item:
                    key, value = item.split('=', 1)
                    # URL解码
                    params[key] = self._url_decode(value)
            
            # 更新配置
            config["device_id"] = params.get("device_id", "").strip()
            config["wifi_ssid"] = params.get("wifi_ssid", "").strip()
            config["wifi_password"] = params.get("wifi_password", "")
            config["device_name"] = params.get("device_name", "ESP32智能设备").strip()
            config["server_mode"] = params.get("server_mode", "cloud")
            config["server_host_local"] = params.get("server_host_local", "192.168.1.100").strip()
            config["server_host_cloud"] = params.get("server_host_cloud", "115.190.181.211").strip()
            config["server_port"] = int(params.get("server_port", "11451"))
            config["api_key"] = params.get("api_key", "").strip()
            
            # 验证配置
            if not ConfigManager.validate(config):
                return {'success': False, 'error': '配置验证失败'}
            
            # 保存配置
            if ConfigManager.save(config):
                return {'success': True}
            else:
                return {'success': False, 'error': '保存文件失败'}
            
        except Exception as e:
            print("[配置] 保存失败: %s" % e)
            return {'success': False, 'error': str(e)}
    
    def _generate_config_page(self):
        """生成配置页面HTML"""
        config = ConfigManager.load()
        
        # 获取当前配置值
        device_id = config.get("device_id", "")
        wifi_ssid = config.get("wifi_ssid", "")
        wifi_password = config.get("wifi_password", "")
        device_name = config.get("device_name", "ESP32智能设备")
        cloud_selected = "selected" if config.get("server_mode") == "cloud" else ""
        local_selected = "selected" if config.get("server_mode") == "local" else ""
        server_host_local = config.get("server_host_local", "192.168.1.100")
        server_host_cloud = config.get("server_host_cloud", "115.190.181.211")
        server_port = str(config.get("server_port", 11451))
        api_key = config.get("api_key", "")
        
        # 生成MAC地址提示
        mac_suffix = ConfigManager.generate_ap_suffix()
        device_id_placeholder = "ESP32-%s" % mac_suffix
        
        # 分段构建HTML
        html = "HTTP/1.1 200 OK\r\n"
        html += "Content-Type: text/html; charset=utf-8\r\n\r\n"
        html += "<!DOCTYPE html><html><head>"
        html += '<meta charset="utf-8">'
        html += '<meta name="viewport" content="width=device-width,initial-scale=1">'
        html += '<title>ESP32配置</title><style>'
        html += '*{margin:0;padding:0;box-sizing:border-box}'
        html += 'body{font-family:Arial,sans-serif;background:#667eea;min-height:100vh;padding:20px}'
        html += '.container{max-width:500px;margin:0 auto;background:#fff;border-radius:15px;box-shadow:0 10px 40px rgba(0,0,0,0.2)}'
        html += '.header{background:#667eea;color:#fff;padding:30px;text-align:center;border-radius:15px 15px 0 0}'
        html += '.header h1{font-size:24px;margin-bottom:10px}'
        html += '.header p{font-size:14px;opacity:0.9}'
        html += '.form{padding:30px}'
        html += '.section{margin-bottom:25px}'
        html += '.section-title{font-size:16px;font-weight:bold;color:#667eea;margin-bottom:15px;padding-bottom:8px;border-bottom:2px solid #f0f0f0}'
        html += '.field{margin-bottom:15px}'
        html += 'label{display:block;margin-bottom:5px;color:#555;font-size:14px}'
        html += '.required{color:#e74c3c;margin-left:3px}'
        html += 'input,select{width:100%;padding:12px;border:2px solid #e0e0e0;border-radius:8px;font-size:14px;transition:border-color 0.3s}'
        html += 'input:focus,select:focus{outline:none;border-color:#667eea}'
        html += '.hint{font-size:12px;color:#999;margin-top:4px}'
        html += '.btn{width:100%;padding:15px;background:#667eea;color:#fff;border:none;border-radius:8px;font-size:16px;font-weight:bold;cursor:pointer;transition:background 0.3s}'
        html += '.btn:hover{background:#5568d3}'
        html += '.btn:active{transform:scale(0.98)}'
        html += '.tip{background:#f8f9fa;padding:15px;border-radius:8px;font-size:13px;color:#666;margin-top:20px;border-left:4px solid #667eea}'
        html += '</style></head><body><div class="container">'
        html += '<div class="header"><h1>🔧 ESP32 设备配置</h1><p>请填写以下信息完成设备配置</p></div>'
        html += '<form class="form" method="POST" action="/save">'
        
        # 设备信息
        html += '<div class="section"><div class="section-title">📱 设备信息</div>'
        html += '<div class="field"><label>设备ID <span class="required">*</span></label>'
        html += '<input type="text" name="device_id" value="' + device_id + '" placeholder="' + device_id_placeholder + '" required minlength="3">'
        html += '<div class="hint">唯一标识符，建议使用: ' + device_id_placeholder + '</div></div>'
        html += '<div class="field"><label>设备名称</label>'
        html += '<input type="text" name="device_name" value="' + device_name + '" placeholder="例如: 客厅屏幕">'
        html += '<div class="hint">用于显示的友好名称</div></div></div>'
        
        # WiFi设置
        html += '<div class="section"><div class="section-title">📶 WiFi 设置</div>'
        html += '<div class="field"><label>WiFi 名称 (SSID) <span class="required">*</span></label>'
        html += '<input type="text" name="wifi_ssid" value="' + wifi_ssid + '" placeholder="输入WiFi名称" required></div>'
        html += '<div class="field"><label>WiFi 密码 <span class="required">*</span></label>'
        html += '<input type="password" name="wifi_password" value="' + wifi_password + '" placeholder="输入WiFi密码" required></div></div>'
        
        # 服务器设置
        html += '<div class="section"><div class="section-title">🌐 服务器设置</div>'
        html += '<div class="field"><label>服务器模式</label><select name="server_mode">'
        html += '<option value="cloud" ' + cloud_selected + '>云端服务器</option>'
        html += '<option value="local" ' + local_selected + '>本地服务器</option></select></div>'
        html += '<div class="field"><label>本地服务器地址</label>'
        html += '<input type="text" name="server_host_local" value="' + server_host_local + '" placeholder="192.168.1.100"></div>'
        html += '<div class="field"><label>云端服务器地址</label>'
        html += '<input type="text" name="server_host_cloud" value="' + server_host_cloud + '" placeholder="115.190.181.211"></div>'
        html += '<div class="field"><label>服务器端口</label>'
        html += '<input type="number" name="server_port" value="' + server_port + '" placeholder="11451"></div>'
        html += '<div class="field"><label>API 密钥</label>'
        html += '<input type="text" name="api_key" value="' + api_key + '" placeholder="选填"></div></div>'
        
        # 提交按钮
        html += '<button type="submit" class="btn">💾 保存配置并重启</button>'
        html += '<div class="tip">💡 提示：保存后设备将自动重启并连接WiFi。请确保信息填写正确。</div>'
        html += '</form></div></body></html>'
        
        return html
    
    def _generate_success_page(self):
        """生成成功页面"""
        html = "HTTP/1.1 200 OK\r\n"
        html += "Content-Type: text/html; charset=utf-8\r\n\r\n"
        html += "<!DOCTYPE html><html><head>"
        html += '<meta charset="utf-8">'
        html += '<meta name="viewport" content="width=device-width,initial-scale=1">'
        html += '<title>配置成功</title><style>'
        html += 'body{font-family:Arial,sans-serif;background:#667eea;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}'
        html += '.box{background:#fff;padding:50px;border-radius:15px;text-align:center;max-width:400px}'
        html += '.icon{font-size:64px;margin-bottom:20px}'
        html += 'h1{color:#27ae60;font-size:28px;margin-bottom:20px}'
        html += 'p{color:#555;font-size:16px;line-height:1.8;margin:10px 0}'
        html += '.loading{display:inline-block;width:20px;height:20px;border:3px solid #f3f3f3;border-top:3px solid #667eea;border-radius:50%;animation:spin 1s linear infinite;margin:20px auto}'
        html += '@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}'
        html += '</style></head><body><div class="box">'
        html += '<div class="icon">✅</div>'
        html += '<h1>配置成功！</h1>'
        html += '<p>设备正在重启...</p>'
        html += '<div class="loading"></div>'
        html += '<p style="margin-top:20px">请稍后重新连接到您的WiFi网络</p>'
        html += '</div></body></html>'
        return html
    
    def _generate_error_page(self, error_msg):
        """生成错误页面"""
        html = "HTTP/1.1 500 Internal Server Error\r\n"
        html += "Content-Type: text/html; charset=utf-8\r\n\r\n"
        html += "<!DOCTYPE html><html><head>"
        html += '<meta charset="utf-8">'
        html += '<meta name="viewport" content="width=device-width,initial-scale=1">'
        html += '<title>配置失败</title><style>'
        html += 'body{font-family:Arial,sans-serif;background:#667eea;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}'
        html += '.box{background:#fff;padding:50px;border-radius:15px;text-align:center;max-width:400px}'
        html += '.icon{font-size:64px;margin-bottom:20px}'
        html += 'h1{color:#e74c3c;font-size:28px;margin-bottom:20px}'
        html += 'p{color:#555;font-size:16px;line-height:1.6;margin:10px 0}'
        html += '.error{background:#ffe6e6;padding:15px;border-radius:8px;color:#c0392b;margin:20px 0;font-size:14px}'
        html += 'a{display:inline-block;margin-top:20px;padding:12px 30px;background:#667eea;color:#fff;text-decoration:none;border-radius:8px;transition:background 0.3s}'
        html += 'a:hover{background:#5568d3}'
        html += '</style></head><body><div class="box">'
        html += '<div class="icon">❌</div>'
        html += '<h1>配置失败</h1>'
        html += '<div class="error">' + error_msg + '</div>'
        html += '<p>请检查输入信息是否正确</p>'
        html += '<a href="/">返回重试</a>'
        html += '</div></body></html>'
        return html
    
    def stop(self):
        """停止配网门户"""
        self.running = False
        if self.server_socket:
            self.server_socket.close()
        if self.ap:
            self.ap.active(False)

# ==================== Unicode 编解码工具 ====================
def encode_unicode(text):
    """将中文字符编码为 \\uXXXX 格式"""
    if not isinstance(text, str):
        return text
    out = []
    for ch in text:
        c = ord(ch)
        out.append('\\u%04x' % c if c > 127 else ch)
    return ''.join(out)

def encode_data(data):
    """递归编码数据结构中的 Unicode 字符"""
    if isinstance(data, str):
        return encode_unicode(data)
    if isinstance(data, list):
        return [encode_data(x) for x in data]
    if isinstance(data, dict):
        return {k: encode_data(v) for k, v in data.items()}
    return data

def decode_unicode(text):
    """解码 \\uXXXX 格式为中文字符"""
    if not isinstance(text, str):
        return text
    res, i, n = [], 0, len(text)
    while i < n:
        if i + 5 < n and text[i:i+2] == '\\u':
            try:
                code = int(text[i+2:i+6], 16)
                res.append(chr(code))
                i += 6
            except:
                res.append(text[i])
                i += 1
        else:
            res.append(text[i])
            i += 1
    return ''.join(res)

def decode_data(data):
    """递归解码数据结构中的 Unicode 字符"""
    if isinstance(data, str):
        return decode_unicode(data)
    if isinstance(data, list):
        return [decode_data(x) for x in data]
    if isinstance(data, dict):
        return {k: decode_data(v) for k, v in data.items()}
    return data

# ==================== 日志系统 ====================
class Logger:
    """分级日志系统"""
    LEVELS = {'DEBUG': 0, 'INFO': 1, 'WARNING': 2, 'ERROR': 3, 'CRITICAL': 4}
    
    def __init__(self, name, level='INFO'):
        self.name = name
        self.level = self.LEVELS.get(level.upper(), 1)
    
    def _log(self, level, msg, data=None):
        if self.LEVELS.get(level, 0) >= self.level:
            print("[%s][%s] %s" % (self.name, level, msg))
            if data:
                print("  └─ %s" % str(data))
    
    def debug(self, msg, data=None):
        self._log('DEBUG', msg, data)
    
    def info(self, msg, data=None):
        self._log('INFO', msg, data)
    
    def warning(self, msg, data=None):
        self._log('WARNING', msg, data)
    
    def error(self, msg, data=None):
        self._log('ERROR', msg, data)
    
    def critical(self, msg, data=None):
        self._log('CRITICAL', msg, data)

# ==================== WebSocket 客户端 ====================
class SimpleWebSocket:
    """轻量级 WebSocket 客户端"""
    OPCODES = {'text': 0x1, 'binary': 0x2, 'close': 0x8, 'ping': 0x9, 'pong': 0xA}
    
    def __init__(self, url):
        self.url = url
        self.sock = None
        self.connected = False
        self.logger = Logger('WebSocket')
    
    def connect(self, api_key=None):
        """建立 WebSocket 连接"""
        try:
            is_wss = self.url.startswith('wss://')
            url = self.url[6:] if is_wss else self.url[5:]
            
            if '/' in url:
                host_port, path = url.split('/', 1)
                path = '/' + path
            else:
                host_port, path = url, '/'
            
            if ':' in host_port:
                host, port = host_port.split(':', 1)
                port = int(port)
            else:
                host, port = host_port, (443 if is_wss else 80)
            
            self.logger.info("连接 %s:%d%s" % (host, port, path))
            
            addr = socket.getaddrinfo(host, port)[0][-1]
            s = socket.socket()
            s.settimeout(5)
            s.connect(addr)
            
            if is_wss:
                import ussl
                s = ussl.wrap_socket(s)
            
            self.sock = s
            key = ubinascii.b2a_base64(bytes(urandom.getrandbits(8) for _ in range(16)))[:-1].decode()
            
            headers = [
                "GET %s HTTP/1.1" % path,
                "Host: %s:%d" % (host, port),
                "Upgrade: websocket",
                "Connection: Upgrade",
                "Sec-WebSocket-Key: %s" % key,
                "Sec-WebSocket-Version: 13"
            ]
            
            if api_key:
                headers.append("X-API-Key: %s" % api_key)
            
            headers.extend(["", ""])
            self.sock.send("\r\n".join(headers).encode())
            
            resp = self._read_http_response()
            
            if resp and "101" in resp.split('\r\n')[0]:
                self.connected = True
                self.logger.info("握手成功")
                return True
            
            self.logger.error("握手失败")
            self.close()
            return False
            
        except Exception as e:
            self.logger.error("连接失败: %s" % e)
            self.close()
            return False
    
    def _read_http_response(self):
        """读取 HTTP 握手响应"""
        try:
            r = b""
            self.sock.settimeout(1)
            
            for _ in range(50):
                try:
                    data = self.sock.recv(1024)
                    if data:
                        r += data
                        if b"\r\n\r\n" in r:
                            return r.decode('utf-8', 'ignore')
                    else:
                        break
                except OSError as e:
                    if len(e.args) > 0 and e.args[0] in (110, 116):
                        time.sleep_ms(100)
                        continue
                    else:
                        raise
            
            return r.decode('utf-8', 'ignore') if r else None
        except:
            return None
    
    def send(self, data):
        """发送 WebSocket 帧"""
        if not self.connected or not self.sock:
            return False
        
        try:
            if isinstance(data, str):
                payload = data.encode()
                opcode = self.OPCODES['text']
            else:
                payload = data
                opcode = self.OPCODES['binary']
            
            ln = len(payload)
            
            if ln < 126:
                header = bytearray((0x80 | opcode, 0x80 | ln))
            elif ln < 65536:
                header = bytearray((0x80 | opcode, 0xFE, (ln >> 8) & 0xFF, ln & 0xFF))
            else:
                header = bytearray(2 + 8)
                header[0] = 0x80 | opcode
                header[1] = 0xFF
                for i in range(8):
                    header[2 + i] = (ln >> (8 * (7 - i))) & 0xFF
            
            mask = bytearray(urandom.getrandbits(8) for _ in range(4))
            self.sock.send(header)
            self.sock.send(mask)
            
            mv = memoryview(payload)
            idx = 0
            step = 1024
            tmp = bytearray(step)
            
            while idx < ln:
                n = step if ln - idx >= step else (ln - idx)
                tmp[0:n] = mv[idx:idx + n]
                for i in range(n):
                    tmp[i] ^= mask[i & 3]
                self.sock.send(tmp[0:n])
                idx += n
            
            return True
            
        except Exception as e:
            self.logger.error("发送失败: %s" % e)
            self.connected = False
            return False
    
    def recv(self, timeout=0.1):
        """接收 WebSocket 帧"""
        if not self.connected or not self.sock:
            return None
        
        try:
            self.sock.settimeout(timeout)
            header = self.sock.recv(2)
            
            if not header or len(header) < 2:
                return None
            
            opcode = header[0] & 0x0F
            masked = (header[1] & 0x80) != 0
            length = header[1] & 0x7F
            
            if length == 126:
                ext = self.sock.recv(2)
                if len(ext) < 2:
                    return None
                length = int.from_bytes(ext, 'big')
            elif length == 127:
                ext = self.sock.recv(8)
                if len(ext) < 8:
                    return None
                length = int.from_bytes(ext, 'big')
            
            mask = None
            if masked:
                mask = self.sock.recv(4)
                if len(mask) < 4:
                    return None
            
            data = b""
            rem = length
            while rem > 0:
                chunk = self.sock.recv(min(rem, 2048))
                if not chunk:
                    break
                data += chunk
                rem -= len(chunk)
            
            if masked and mask:
                data = bytes([data[i] ^ mask[i & 3] for i in range(len(data))])
            
            if opcode == self.OPCODES['close']:
                self.connected = False
                return None
            elif opcode == self.OPCODES['ping']:
                self._send_pong(data)
                return None
            elif opcode == self.OPCODES['text']:
                return data.decode('utf-8')
            elif opcode == self.OPCODES['binary']:
                return data
            
            return None
            
        except OSError as e:
            if len(e.args) > 0 and e.args[0] in (110, 116):
                return None
            self.connected = False
        except Exception as e:
            self.logger.error("接收失败: %s" % e)
            self.connected = False
        
        return None
    
    def _send_pong(self, data=b""):
        """响应 ping 帧"""
        if not self.connected:
            return
        
        try:
            ln = len(data)
            if ln < 126:
                header = bytes([0x80 | self.OPCODES['pong'], 0x80 | ln])
            else:
                header = bytes([0x80 | self.OPCODES['pong'], 0xFE]) + ln.to_bytes(2, 'big')
            
            mask = bytes(urandom.getrandbits(8) for _ in range(4))
            masked = bytes([data[i] ^ mask[i & 3] for i in range(ln)])
            self.sock.send(header + mask + masked)
        except:
            pass
    
    def close(self):
        """关闭连接"""
        if self.sock:
            try:
                if self.connected:
                    frame = bytes([0x88, 0x82]) + bytes(urandom.getrandbits(8) for _ in range(4)) + bytes([0x03, 0xE8])
                    self.sock.send(frame)
                self.sock.close()
            except:
                pass
            finally:
                self.sock = None
                self.connected = False
                gc.collect()

# ==================== 异步 WebSocket ====================
class AsyncWebSocket:
    """异步 WebSocket 封装"""
    
    def __init__(self, url, api_key=None, qmax=64):
        self.url = url
        self.api_key = api_key
        self.ws = None
        self.connected = False
        self.reconnect_delay = 1
        self.max_reconnect_delay = 30
        self.logger = Logger('AsyncWS')
        
        self._send_q = []
        self._q_lock = _thread.allocate_lock()
        self._qmax = qmax
        self._writer_running = False
        self._writer_should_stop = False
    
    def _q_put(self, item):
        with self._q_lock:
            if len(self._send_q) >= self._qmax:
                self._send_q.pop(0)
            self._send_q.append(item)
    
    def _q_get(self):
        with self._q_lock:
            if self._send_q:
                return self._send_q.pop(0)
            return None
    
    def _writer_loop(self):
        self.logger.info("发送线程启动")
        
        while not self._writer_should_stop:
            if not self.connected or not self.ws:
                time.sleep_ms(50)
                continue
            
            item = self._q_get()
            if item is None:
                time.sleep_ms(5)
                continue
            
            try:
                self.ws.send(item)
            except Exception as e:
                self.logger.error("发送失败: %s" % e)
                time.sleep_ms(50)
        
        self.logger.info("发送线程停止")
    
    def _ensure_writer(self):
        if not self._writer_running:
            try:
                self._writer_should_stop = False
                _thread.start_new_thread(self._writer_loop, ())
                self._writer_running = True
            except Exception as e:
                self.logger.error("启动发送线程失败: %s" % e)
    
    async def connect(self):
        try:
            self.ws = SimpleWebSocket(self.url)
            if self.ws.connect(self.api_key):
                self.connected = True
                self.reconnect_delay = 1
                self._ensure_writer()
                gc.collect()
                return True
            return False
        except Exception as e:
            self.logger.error("连接失败: %s" % e)
            return False
    
    async def send_now(self, data):
        if not self.connected or not self.ws:
            return False
        
        if isinstance(data, dict):
            # 确保所有数据都经过Unicode编码
            data = encode_data(data)
            data = json.dumps(data)
        
        ok = self.ws.send(data)
        return ok
    
    def enqueue(self, data):
        if isinstance(data, dict):
            # 确保所有数据都经过Unicode编码
            data = encode_data(data)
            data = json.dumps(data)
        self._q_put(data)
    
    async def receive(self, timeout=0.1):
        if not self.connected or not self.ws:
            return None
        
        try:
            data = self.ws.recv(timeout)
            if data:
                try:
                    parsed = json.loads(data)
                    result = decode_data(parsed)
                    return result
                except Exception:
                    return data
            return None
        except:
            return None
    
    async def close(self):
        self._writer_should_stop = True
        
        if self.ws:
            self.ws.close()
        
        self.connected = False
        gc.collect()
    
    async def auto_reconnect(self):
        attempts = 0
        max_attempts = 10
        
        while not self.connected and attempts < max_attempts:
            attempts += 1
            self.logger.info("重连尝试 %d/%d" % (attempts, max_attempts))
            
            if await self.connect():
                self.logger.info("重连成功")
                return True
            
            await asyncio.sleep(self.reconnect_delay)
            self.reconnect_delay = min(self.reconnect_delay * 2, self.max_reconnect_delay)
        
        return False

# ==================== 插件管理器 ====================
class PluginManager:
    """插件管理器"""
    
    def __init__(self, loader):
        self.loader = loader
        self.config = loader.config
        self.plugins = {}
        self.running = True
        self.logger = Logger('Plugin')
        self.plugin_locks = {}
    
    async def load(self):
        if not self.config.get("plugin_auto_load"):
            return
        
        plugin_dir = self.config.get("plugin_dir", "/plugins")
        self.logger.info("扫描插件: %s" % plugin_dir)
        
        try:
            files = os.listdir(plugin_dir)
        except OSError:
            self.logger.warning("插件目录不存在")
            return
        
        loaded = 0
        
        for file in files:
            if not file.endswith(".py") or file.startswith("_"):
                continue
            
            plugin_name = file[:-3]
            if await self.load_plugin(plugin_name):
                loaded += 1
        
        self.logger.info("加载 %d 个插件" % loaded)
    
    async def load_plugin(self, plugin_name):
        try:
            self.logger.info("加载: %s" % plugin_name)
            
            original_path = sys.path[:]
            
            try:
                plugin_dir = self.config.get("plugin_dir", "/plugins")
                if plugin_dir not in sys.path:
                    sys.path.insert(0, plugin_dir)
                
                plugin_module = __import__(plugin_name)
            finally:
                sys.path[:] = original_path
            
            plugin_class = None
            for attr_name in dir(plugin_module):
                try:
                    attr = getattr(plugin_module, attr_name)
                    if isinstance(attr, type) and attr_name != 'Plugin':
                        if attr_name.lower() == plugin_name.lower() or attr_name == "Plugin":
                            plugin_class = attr
                            break
                except:
                    continue
            
            if not plugin_class:
                self.logger.warning("%s 无有效插件类" % plugin_name)
                return False
            
            plugin = plugin_class(self.loader)
            
            result = await plugin.init()
            
            if result is False:
                self.logger.error("%s 初始化失败" % plugin_name)
                return False
            
            self.plugins[plugin_name] = plugin
            
            if hasattr(plugin, "capabilities"):
                self.loader.capabilities.extend(plugin.capabilities)
            
            self.plugin_locks[plugin_name] = _thread.allocate_lock()
            
            if hasattr(plugin, 'run_in_thread') and plugin.run_in_thread:
                try:
                    _thread.stack_size(16384)
                    _thread.start_new_thread(
                        self._plugin_thread_wrapper,
                        (plugin_name, plugin)
                    )
                    self.logger.info("%s 在独立线程运行" % plugin_name)
                except Exception as e:
                    self.logger.error("%s 启动线程失败: %s" % (plugin_name, e))
            
            self.logger.info("%s 加载成功" % plugin_name)
            await self.loader.send_log('info', '插件 %s 加载成功' % plugin_name)
            
            return True
            
        except Exception as e:
            self.logger.error("%s 加载失败: %s" % (plugin_name, e))
            if self.config.get("debug_mode"):
                sys.print_exception(e)
            return False
    
    def _plugin_thread_wrapper(self, name, plugin):
        self.logger.info("%s 线程启动" % name)
        restart_count = 0
        max_restarts = 3
        
        while self.running and plugin.enabled:
            try:
                plugin.thread_run()
                break
                
            except Exception as e:
                restart_count += 1
                self.logger.error("%s 线程错误 (%d/%d): %s" % (name, restart_count, max_restarts, e))
                
                if self.config.get("debug_mode"):
                    sys.print_exception(e)
                
                if restart_count >= max_restarts:
                    self.logger.critical("%s 失败次数过多，禁用插件" % name)
                    plugin.enabled = False
                    break
                
                time.sleep(5)
        
        self.logger.info("%s 线程停止" % name)
    
    async def run(self):
        while self.running:
            for name, plugin in list(self.plugins.items()):
                if not plugin.enabled:
                    continue
                
                if hasattr(plugin, 'run_in_thread') and plugin.run_in_thread:
                    continue
                
                try:
                    await plugin.safe_run()
                except Exception as e:
                    self.logger.error("%s 运行错误: %s" % (name, e))
                    if self.config.get("debug_mode"):
                        sys.print_exception(e)
            
            await asyncio.sleep(1)
    
    async def cleanup(self):
        self.logger.info("清理插件")
        self.running = False
        
        for name, plugin in list(self.plugins.items()):
            try:
                await plugin.cleanup()
            except Exception as e:
                self.logger.error("%s 清理失败: %s" % (name, e))
        
        self.plugins.clear()

# ==================== 设备加载器 ====================
class DeviceLoader:
    """设备加载器主类"""
    
    def __init__(self, config):
        self.config = config
        self.device_id = config.get("device_id")
        self.device_type = config.get("device_type")
        self.device_name = config.get("device_name")
        self.logger = Logger('Loader', level=config.get("log_level", "INFO"))
        
        self.plugin_manager = PluginManager(self)
        
        self.running = True
        self.is_registered = False
        self.start_time = time.ticks_ms()
        
        self.capabilities = []
        self.metadata = {}
        self.event_buffer = []
        self.command_handlers = {}
        
        self.wdt = None
        self.ws = None
        self.ip_address = None
        self.tasks = []
        
        self.stats = {
            'messages_sent': 0,
            'messages_received': 0,
            'commands_executed': 0,
            'errors': 0
        }
        
        global _device_loader
        _device_loader = self
    
    def setup_network(self, ip, gateway=None):
        self.ip_address = ip
        host = self.config.get("server_host_local") if self.config.get("server_mode") == "local" else self.config.get("server_host_cloud")
        port = self.config.get("server_port")
        self.ws_url = "ws://%s:%d/device" % (host, port)
        self.ws = AsyncWebSocket(
            self.ws_url,
            self.config.get("api_key"),
            qmax=self.config.get("ws_send_queue_size", 64)
        )
    
    def init_watchdog(self):
        if self.config.get("watchdog_enabled"):
            try:
                timeout = self.config.get("watchdog_timeout", 30000)
                self.wdt = WDT(timeout=timeout)
                self.logger.info("看门狗启动 (超时: %dms)" % timeout)
            except Exception as e:
                self.logger.error("看门狗失败: %s" % e)
    
    def feed_watchdog(self):
        if self.wdt:
            try:
                self.wdt.feed()
            except Exception as e:
                self.logger.error("喂狗失败: %s" % e)
    
    def register_command(self, cmd, handler):
        self.command_handlers[cmd] = handler
        self.logger.debug("注册命令: %s" % cmd)
    
    async def send_log(self, level, message, data=None):
        if not self.config.get("log_to_server"):
            return True
        
        log_data = {
            "device_id": self.device_id,
            "type": "log",
            "level": level,
            "message": message,  # 会在enqueue时自动encode
            "data": data or {},
            "timestamp": time.time()
        }
        
        if self.ws and self.ws.connected:
            self.ws.enqueue(log_data)
            return True
        else:
            buffer_size = self.config.get("event_buffer_size", 50)
            self.event_buffer.append(log_data)
            if len(self.event_buffer) > buffer_size:
                self.event_buffer.pop(0)
            return False
    
    async def send_data(self, data_type, data):
        event = {
            "device_id": self.device_id,
            "type": "data",
            "data_type": data_type,
            "data": data,  # 会在enqueue时自动encode
            "timestamp": time.time()
        }
        
        self.stats['messages_sent'] += 1
        
        if self.ws and self.ws.connected:
            self.ws.enqueue(event)
            return True
        else:
            buffer_size = self.config.get("event_buffer_size", 50)
            self.event_buffer.append(event)
            if len(self.event_buffer) > buffer_size:
                self.event_buffer.pop(0)
            return False
    
    async def flush_events(self):
        if not self.event_buffer or not self.ws or not self.ws.connected:
            return
        
        cnt = 0
        while self.event_buffer and cnt < 5:
            ev = self.event_buffer.pop(0)
            self.ws.enqueue(ev)
            cnt += 1
    
    async def handle_command(self, command):
        try:
            cmd_type = command.get("command")
            params = decode_data(command.get("parameters", {}))
            
            self.stats['commands_executed'] += 1
            self.logger.info("执行命令: %s" % cmd_type)
            
            if cmd_type == "reboot":
                await self.send_log('info', '设备重启')
                await asyncio.sleep(1)
                machine.reset()
                
            elif cmd_type == "gc":
                free_before = gc.mem_free()
                gc.collect()
                free_after = gc.mem_free()
                return {
                    "success": True,
                    "free": free_after,
                    "freed": free_after - free_before
                }
                
            elif cmd_type == "stats":
                return {
                    "success": True,
                    "stats": self.stats,
                    "uptime": time.ticks_diff(time.ticks_ms(), self.start_time) // 1000,
                    "memory": gc.mem_free()
                }
            
            if cmd_type in self.command_handlers:
                result = await self.command_handlers[cmd_type](params)
                return result
            
            for plugin in self.plugin_manager.plugins.values():
                if not plugin.enabled:
                    continue
                
                result = await plugin.safe_handle_command(cmd_type, params)
                if result is not None:
                    return result
            
            return {"success": False, "error": "未知命令"}
            
        except Exception as e:
            self.stats['errors'] += 1
            self.logger.error("命令失败: %s" % str(e))
            
            if self.config.get("debug_mode"):
                sys.print_exception(e)
            
            return {"success": False, "error": str(e)}
    
    async def register_device(self):
        self.logger.info("设备注册")
        
        self.collect_capabilities()
        
        # 注册数据 - 需要encode_data确保中文正确传输
        reg_data = {
            "type": "register",
            "device_id": self.device_id,
            "device_type": self.device_type,
            "device_name": self.device_name,  # 会自动编码中文
            "capabilities": self.capabilities,
            "metadata": self.metadata,
            "firmware_version": self.config.get("firmware_version"),
            "ip_address": self.ip_address
        }
        
        if not self.ws or not self.ws.connected:
            if not await self.ws.connect():
                return False
        
        # send_now会自动调用encode_data
        if await self.ws.send_now(reg_data):
            timeout_ms = 5000
            start = time.ticks_ms()
            
            while time.ticks_diff(time.ticks_ms(), start) < timeout_ms:
                response = await self.ws.receive(0.1)
                
                if response and isinstance(response, dict):
                    if response.get("type") == "register_response":
                        if response.get("success"):
                            self.is_registered = True
                            self.logger.info("注册成功")
                            return True
                        else:
                            self.logger.error("注册失败")
                            return False
                
                await asyncio.sleep_ms(100)
        
        return False
    
    def collect_capabilities(self):
        self.capabilities = ["gpio", "sensor"]
        
        if self.ws:
            self.capabilities.append("websocket")
        
        self.metadata = {
            "platform": sys.platform,
            "heap_free": gc.mem_free()
        }
    
    async def websocket_task(self):
        if not self.ws:
            return
        
        self.logger.info("WebSocket任务启动")
        last_heartbeat = time.ticks_ms()
        heartbeat_interval = self.config.get("heartbeat_interval", 30) * 1000
        
        while self.running:
            try:
                if not self.ws.connected:
                    self.logger.warning("连接断开")
                    if await self.ws.auto_reconnect():
                        if not self.is_registered:
                            await self.register_device()
                    else:
                        await asyncio.sleep(30)
                        continue
                
                msg = await self.ws.receive(0.1)
                
                if msg:
                    self.stats['messages_received'] += 1
                    
                    if isinstance(msg, dict):
                        msg_type = msg.get("type")
                        
                        if msg_type == "command":
                            command = msg.get("command")
                            result = await self.handle_command(command)
                            
                            # 命令结果也需要encode
                            self.ws.enqueue({
                                "type": "command_result",
                                "command_id": command.get("id"),
                                "result": result
                            })
                            
                        elif msg_type == "heartbeat_request":
                            # 心跳响应 - 确保status中的数据被正确编码
                            self.ws.enqueue({
                                "type": "heartbeat",
                                "device_id": self.device_id,
                                "status": self.get_status()
                            })
                            last_heartbeat = time.ticks_ms()
                
                if time.ticks_diff(time.ticks_ms(), last_heartbeat) > heartbeat_interval:
                    # 主动心跳 - 确保数据被正确编码
                    self.ws.enqueue({
                        "type": "heartbeat",
                        "device_id": self.device_id,
                        "timestamp": time.time(),
                        "status": self.get_status()
                    })
                    last_heartbeat = time.ticks_ms()
                
                await self.flush_events()
                
            except Exception as e:
                if self.ws and self.ws.connected:
                    self.logger.error("WebSocket错误: %s" % e)
                    self.ws.connected = False
                
                self.stats['errors'] += 1
                
                if self.config.get("debug_mode"):
                    sys.print_exception(e)
            
            await asyncio.sleep_ms(100)
    
    def get_status(self):
        return {
            "heap_free": gc.mem_free(),
            "uptime": time.ticks_diff(time.ticks_ms(), self.start_time) // 1000,
            "stats": self.stats
        }
    
    async def watchdog_task(self):
        gc_counter = 0
        gc_interval = self.config.get("gc_interval", 30)
        feed_interval = self.config.get("watchdog_feed_interval", 5)
        
        while self.running:
            self.feed_watchdog()
            
            gc_counter += 1
            if gc_counter >= gc_interval:
                free_before = gc.mem_free()
                gc.collect()
                free_after = gc.mem_free()
                
                if self.config.get("debug_mode") and free_after - free_before > 1000:
                    self.logger.debug("GC回收: %d bytes" % (free_after - free_before))
                
                gc_counter = 0
            
            await asyncio.sleep(feed_interval)
    
    async def cleanup(self):
        self.logger.info("清理资源")
        self.running = False
        
        for t in self.tasks:
            t.cancel()
            try:
                await t
            except asyncio.CancelledError:
                pass
        
        await self.plugin_manager.cleanup()
        
        if self.ws:
            await self.ws.close()
        
        gc.collect()
    
    async def main(self):
        print("\n" + "=" * 60)
        print("葵宝设备加载器 v%s".center(60) % self.config.get("firmware_version"))
        print("=" * 60)
        print("[设备] ID: %s" % self.device_id)
        print("[设备] 名称: %s" % self.device_name)
        print("[网络] IP: %s" % self.ip_address)
        print("[内存] 可用: %d bytes" % gc.mem_free())
        print("=" * 60 + "\n")
        
        self.init_watchdog()
        
        for retry in range(3):
            if retry > 0:
                delay = 2 ** retry
                self.logger.info("等待 %d 秒" % delay)
                await asyncio.sleep(delay)
            
            self.logger.info("注册尝试 %d/3" % (retry + 1))
            if await self.register_device():
                break
        else:
            self.logger.critical("注册失败")
            await asyncio.sleep(5)
            machine.reset()
        
        self.logger.info("加载插件")
        await self.plugin_manager.load()
        self.logger.info("插件数: %d" % len(self.plugin_manager.plugins))
        
        self.tasks = []
        
        if self.ws:
            self.tasks.append(asyncio.create_task(self.websocket_task()))
        
        if self.plugin_manager.plugins:
            self.tasks.append(asyncio.create_task(self.plugin_manager.run()))
        
        self.tasks.append(asyncio.create_task(self.watchdog_task()))
        
        print("=" * 60)
        print("系统运行中".center(60))
        print("=" * 60 + "\n")
        
        try:
            await asyncio.gather(*self.tasks)
        except KeyboardInterrupt:
            self.logger.info("用户停止")
        except Exception as e:
            self.logger.critical("致命错误: %s" % e)
            if self.config.get("debug_mode"):
                sys.print_exception(e)
            await asyncio.sleep(5)
            machine.reset()
        finally:
            await self.cleanup()

# ==================== WiFi 连接 ====================
def connect_wifi(config):
    """连接 WiFi"""
    logger = Logger('WiFi')
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    
    if not wlan.isconnected():
        logger.info("连接WiFi")
        ssid = config.get("wifi_ssid")
        password = config.get("wifi_password")
        
        logger.info("SSID: %s" % ssid)
        
        wlan.connect(ssid, password)
        
        timeout = config.get("wifi_timeout", 30)
        while timeout > 0:
            if wlan.isconnected():
                break
            time.sleep(1)
            timeout -= 1
        
        if not wlan.isconnected():
            logger.critical("WiFi超时")
            return None, None
    
    ifconfig = wlan.ifconfig()
    logger.info("连接成功")
    logger.info("IP: %s" % ifconfig[0])
    
    return ifconfig[0], ifconfig[2]

# ==================== 检测Boot按钮 ====================
def check_boot_button():
    """检测Boot按钮是否按下（GPIO 0）"""
    try:
        boot_pin = Pin(0, Pin.IN, Pin.PULL_UP)
        # Boot按钮按下时为低电平
        if boot_pin.value() == 0:
            print("[Boot] 检测到按钮按下")
            return True
        return False
    except:
        return False

# ==================== 主入口 ====================
def main():
    """主入口函数"""
    try:
        # 检查是否需要进入配网模式
        need_config = False
        
        # 1. 检查配置文件是否存在
        if not ConfigManager.exists():
            print("[配置] 未找到配置文件")
            need_config = True
        
        # 2. 检查Boot按钮
        if check_boot_button():
            print("[配置] Boot按钮触发配网")
            need_config = True
            time.sleep(1)  # 防抖
        
        # 进入配网模式
        if need_config:
            portal = ConfigPortal()
            portal.start()
            return
        
        # 加载配置
        config = ConfigManager.load()
        
        # 验证配置完整性
        if not ConfigManager.validate(config):
            print("[错误] 配置不完整，进入配网模式")
            portal = ConfigPortal()
            portal.start()
            return
        
        # 连接 WiFi
        ip, gateway = connect_wifi(config)
        
        if not ip:
            print("[错误] WiFi连接失败，进入配网模式")
            portal = ConfigPortal()
            portal.start()
            return
        
        # 创建加载器
        loader = DeviceLoader(config)
        loader.setup_network(ip, gateway)
        
        # 运行
        asyncio.run(loader.main())
        
    except KeyboardInterrupt:
        print("\n[退出]")
    except Exception as e:
        print("[错误] %s" % e)
        sys.print_exception(e)
        time.sleep(5)
        machine.reset()

if __name__ == "__main__":
    main()